from __future__ import (absolute_import, division, print_function,
                        unicode_literals)

import datetime  # For datetime objects
import os.path  # To manage paths
import sys  # To find out the script name (in argv[0])

# 获取当前脚本的绝对路径，并找到父目录
current_dir = os.path.dirname(os.path.abspath(__file__))
parent_dir = os.path.dirname(current_dir)
# 将父目录添加到 Python 模块搜索路径
sys.path.append(parent_dir)
# Import the backtrader platform
import backtrader as bt

class TestStrategy(bt.Strategy): 
    params = ( 
        # 持仓够5个单位就卖出  
        ('exitbars', 5), 
        # 均线参数设置15天，15日均线  
        ('maperiod', 15), 
    )
    def log(self, txt, dt=None): 
        # 记录策略的执行日志  
        dt = dt or self.datas[0].datetime.date(0) 
        print('%s, %s' % (dt.isoformat(), txt)) 

    def __init__(self): 
        # 保存收盘价的引用  
        self.dataclose = self.datas[0].close
        self.dataopen = self.datas[0].open
        # 跟踪挂单  
        self.order = None 
        # 买入价格和手续费  
        self.buyprice = None 
        self.buycomm = None
         # 加入均线指标，用于计算指定周期内的价格的平均值。默认使用close价格计算SMA，也可以通过参数datafield指定其他字段，比如open  
        self.sma = bt.indicators.SimpleMovingAverage(self.datas[0], period=self.params.maperiod)

        """
        即使指标没有被显式地声明为成员变量（如 self.sma = MovingAverageSimple…）， 它们还是会被自动注册到策略类中，
        并影响开始执行next的最小周期，而且会被绘制。 在例子中，只有RSI的指标被赋予了一个rsi的变量，
        供后边为它创建移动平均线使用。
        """
        # 绘制图形时候用到的指标
        # 指数移动平均线（EMA）指标，周期为 25。EMA 是一种常用的趋势跟踪指标，它对最近的价格赋予更高的权重。
        bt.indicators.ExponentialMovingAverage(self.datas[0], period=25) 
        # 加权移动平均线（WMA）指标，周期为 25。subplot = True 表示这个指标将显示在子图中，而不是主价格图上。
        # WMA 也是一种趋势跟踪指标，它对最近的价格赋予更高的权重，但与 EMA 的计算方式不同。
        bt.indicators.WeightedMovingAverage(self.datas[0], period=25,subplot=True) 
        # 创建了一个慢速随机振荡器（Stochastic Slow）指标。
        # 随机振荡器用于衡量价格相对于一定时间范围内的最高价和最低价的位置，通常用于识别超买或超卖条件。
        bt.indicators.StochasticSlow(self.datas[0]) 
        # 创建了一个 MACD 柱状图（MACD Histogram）指标。
        # MACD（移动平均收敛 divergence）是一种趋势跟踪动量指标，显示两个移动平均线之间的差异。MACD 柱状图是 MACD 与其信号线之间的差异。
        bt.indicators.MACDHisto(self.datas[0]) 
        # 创建了一个相对强弱指数（RSI）指标。RSI 是一种动量振荡器，用于衡量价格变动的速度和变化，通常用于识别超买或超卖条件。
        rsi = bt.indicators.RSI(self.datas[0]) 
        # 创建了 RSI 的平滑移动平均线（Smoothed Moving Average），周期为10。平滑移动平均线是一种移动平均线，它对价格数据进行了额外的平滑处理。
        bt.indicators.SmoothedMovingAverage(rsi, period=10) 
        # 创建了一个平均真实波动幅度（ATR）指标。plot = False 表示这个指标不会在图表上显示。ATR 用于衡量市场波动性，通常用于设置止损或确定仓位大小。
        bt.indicators.ATR(self.datas[0], plot=False)
        
    # 订单状态通知，买入卖出都是下单  
    def notify_order(self, order): 
        if order.status in [order.Submitted, order.Accepted]:
            # broker 提交/接受了，买/卖订单则什么都不做  
            return 
        # 检查一个订单是否完成  
        # 注意: 当资金不足时，broker会拒绝订单  
        if order.status in [order.Completed]: 
            if order.isbuy(): 
                self.log( 
                    '已买入, 价格: %.2f, 费用: %.2f, 佣金 %.2f' % 
                    (order.executed.price, 
                    order.executed.value, 
                    order.executed.comm)) 
                self.buyprice = order.executed.price 
                self.buycomm = order.executed.comm 
            elif order.issell(): 
                self.log('已卖出, 价格: %.2f, 费用: %.2f, 佣金 %.2f' % 
                    (order.executed.price, 
                    order.executed.value, 
                    order.executed.comm)) 
            # 记录买入时的K线位置 
            self.bar_executed = len(self) 

        elif order.status in [order.Canceled, order.Margin, order.Rejected]: 
            self.log('订单取消/保证金不足/拒绝')

        # 其他状态记录为：无挂起订单  
        self.order = None 


    # 交易状态通知，一买一卖算交易  
    def notify_trade(self, trade): 
        if not trade.isclosed: 
            return 
        self.log('交易利润, 毛利润 %.2f, 净利润 %.2f' % 
            (trade.pnl, trade.pnlcomm)) 

    def next(self): 
        # 记录收盘价  
        self.log('Open, %.2f, Close, %.2f' % (self.dataopen[0], self.dataclose[0])) 
        # 如果有订单正在挂起，不操作  
        if self.order: 
            return 
    # 如果没有持仓则买入  
        if not self.position: 
             # 今天的收盘价在均线价格之上
             # 每当新数据到达时自动更新SMA，无需手动维护计算逻辑，self.sma[0]获取最新的sma值，sma[-1]获取前一日的sma值  
            if self.dataclose[0] > self.sma[0]: 
                # 买入  
                self.log('买入单, close: %.2f, sma: %.2f' % (self.dataclose[0], self.sma[0])) 
                # 跟踪订单避免重复  
                self.order = self.buy()
        else:
            # 如果已经持仓，且收盘价在均线价格之下  
            if self.dataclose[0] < self.sma[0]: 
                # 全部卖出
                self.log('卖出单, close: %.2f, sma: %.2f' % (self.dataclose[0], self.sma[0])) 
                # 跟踪订单避免重复  
                self.order = self.sell()

if __name__ == '__main__':
    # 创建Cerebro引擎  
    cerebro = bt.Cerebro() 
    # Cerebro引擎在后台创建broker(经纪人)，系统默认资金量为10000 
    # 为Cerebro引擎添加策略  
    cerebro.addstrategy(TestStrategy, exitbars=5) 
    # 获取当前运行脚本所在目录  
    modpath = os.path.dirname(os.path.abspath(sys.argv[0])) 
    # 拼接加载路径  
    datapath = os.path.join(modpath, '../datas/orcl-1995-2014.txt') 
    # 创建交易数据集  
    data = bt.feeds.YahooFinanceCSVData( 
                                    dataname=datapath, 
                                    # 数据必须大于fromdate 
                                    fromdate=datetime.datetime(2000, 1, 1), 
                                    # 数据必须小于todate 
                                    todate=datetime.datetime(2000, 12, 31), 
                                    reverse=False) 
    # 加载交易数据  
    cerebro.adddata(data) 
    # 设置投资金额1000.0 
    cerebro.broker.setcash(1000.0) 
    # 每笔交易使用固定交易量 - 买卖数量改为了原来的 10倍，盈亏也变为了原来的10倍。
    # 根据传入的参加设置买卖数量
    cerebro.addsizer(bt.sizers.FixedSize, stake=10)
    # 设置佣金为0.0 
    cerebro.broker.setcommission(commission=0.0) 
    # 引擎运行前打印期出资金  
    print('组合期初资金: %.2f' % cerebro.broker.getvalue()) 
    cerebro.run() 
    # 引擎运行后打印期末资金  
    print('组合期末资金: %.2f' % cerebro.broker.getvalue())
    cerebro.plot()

"""
框架会等待所有指标数据到位之后，才会运行 next函数。 在上例中，MACD是最后一个数据到位的指标（它的3条线都完成了输出）。 
所以第一笔下单已经不是 2000年1月份了，而是2000年2月份末。  
"""